package com.yingwei.mining.server;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.yingwei.mining.control.Constant;
import com.yingwei.mining.control.Data;
import com.yingwei.mining.control.DataScheduler;
import com.yingwei.mining.model.businessobject.Configuration;
import com.yingwei.mining.model.businessobject.Edge;
import com.yingwei.mining.model.businessobject.Excavator;
import com.yingwei.mining.model.businessobject.Node;
import com.yingwei.mining.model.businessobject.Truck;
import com.yingwei.mining.model.businessobject.TruckStatus;
import com.yingwei.mining.model.businessobject.WorkOrder;
import com.yingwei.mining.simulator.TruckRobot;
import com.yingwei.mining.util.HibernateUtil;
import com.yingwei.mining.util.LocationUtil;

public class Daemon
{
    private static Logger logger = LoggerFactory.getLogger(Daemon.class);
    Data data;

    /**
     * @param args
     */
    public static void main(String[] args)
    {
	Daemon daemon = new Daemon();
	daemon.run();
    }

    public Daemon()
    {
	data = Data.getData();
    }

    public void run()
    {
	data.init();
	data.postInitProcess();
	data.reCalculateAllExistingRoutes();
	try
	{
	    while (true)
	    {
		data.clear();
		calculateWorkOrder();
		Thread.sleep(10000);
	    }
	}
	catch (InterruptedException e)
	{
	    logger.error("error in work order schedule", e);
	}
	finally
	{
	    data.close();
	}
    }

    public void calculateWorkOrder()
    {
	for (Truck tr : data.getTrucks())
	{
	    if (tr.getStatus().getStatusId().equals(
		    Constant.TRUCK_STATUS_WAIT_INSTRUCTION))
	    {
		WorkOrder wo = new DataScheduler().createNextWorkOrder(tr);
		if (wo != null)
		{
		    data.addWorkOrder(wo);
		    TruckStatus ts = data
			    .getTruckStatus(Constant.TRUCK_STATUS_MOVE_TO_LOAD);
		    tr.setStatus(ts);
		    data.update(tr);
		    data.flush();
		}
	    }
	}
    }

    /*
    public void simulateTruck(Truck truck)
    {
	WorkOrder workOrder = null;
	for (WorkOrder wo : data.getWorkOrders())
	{
	    if (wo.getTruck().getId() == truck.getId())
	    {
		if (wo.getStatus().equalsIgnoreCase(
			Constant.WORK_ORDER_STATUS_SCHEDULED))
		{
		    workOrder = wo;
		    break;
		}
	    }
	}
	if (workOrder == null)
	{
	    return;
	}
	if (truck.getStatus().getStatusId().equals(
		Constant.TRUCK_STATUS_MOVE_TO_LOAD))
	{
	    truckMoveToLoad(truck, workOrder);
	}
	else if (truck.getStatus().getStatusId().equals(
		Constant.TRUCK_STATUS_WAIT_LOAD))
	{
	    truckWaitLoad(truck, workOrder);
	}
	else if (truck.getStatus().getStatusId().equals(
		Constant.TRUCK_STATUS_LOAD))
	{
	    truckLoad(truck, workOrder);
	}
	else if (truck.getStatus().getStatusId().equals(
		Constant.TRUCK_STATUS_MOVE_TO_UNLOAD))
	{
	    truckMoveToUnload(truck, workOrder);
	}
	else if (truck.getStatus().getStatusId().equals(
		Constant.TRUCK_STATUS_WAIT_UNLOAD))
	{
	    truckWaitUnload(truck, workOrder);
	}
	else if (truck.getStatus().getStatusId().equals(
		Constant.TRUCK_STATUS_UNLOAD))
	{
	    truckUnload(truck, workOrder);
	}
    }

    void truckMoveToLoad(Truck truck, WorkOrder workOrder)
    {
	int x = truck.getX();
	int y = truck.getY();
	long lastModifyTime = truck.getModifyTime().getTime();
	long now = System.currentTimeMillis();
	Edge edge = this.findOnEdge(x, y, workOrder.getLoadRoute().getEdges());
	if (edge == null)
	{
	    // simply move the truck to the nearest node. this will
	    // happen at initial state.
	    Node node = LocationUtil.findNearbyNode(x, y, workOrder
		    .getLoadRoute().getEdges());
	    x = node.getX();
	    y = node.getY();
	    edge = this.findOnEdge(node.getX(), node.getY(), workOrder
		    .getLoadRoute().getEdges());
	}
	// calculate next position.
	double speed = truck.getModel().getCruisingSpeed();
	double deltaDistance = speed * (now - lastModifyTime) / 3600;
	double dx = x - edge.getToNode().getX();
	double dy = y - edge.getToNode().getY();
	double dist = Math.sqrt(dx * dx + dy * dy);
	int nextPositionX = 0;
	int nextPositionY = 0;

	if (dist > 0.0)
	{
	    if (deltaDistance >= dist)
	    {
		nextPositionX = edge.getToNode().getX();
		nextPositionY = edge.getToNode().getY();
		if (edge.getToNode().getId() == workOrder.getLoadRoute()
			.getNodeTo().getId())
		{
		    truck.setStatus(data
			    .getTruckStatus(Constant.TRUCK_STATUS_WAIT_LOAD));
		}
	    }
	    else
	    {
		nextPositionX = (int) ((edge.getToNode().getX() - x)
			* (deltaDistance / dist) + x);
		nextPositionY = (int) ((edge.getToNode().getY() - y)
			* (deltaDistance / dist) + y);
	    }
	    truck.setX(nextPositionX);
	    truck.setY(nextPositionY);
	    truck.setModifyTime(new Date());
	    data.update(truck);
	    data.flush();
	}
	else
	{
	    // reached the destination, assertion is x,y is at the excavator
	    // position.
	    if (workOrder.getLoadRoute().getNodeTo().getX() != x
		    || workOrder.getLoadRoute().getNodeTo().getY() != y)
	    {
		logger
			.error("reached the destination, but x,y is not at the excavator position.");
	    }
	    truck.setModifyTime(new Date());
	    truck.setStatus(data
		    .getTruckStatus(Constant.TRUCK_STATUS_WAIT_LOAD));
	    data.update(truck);
	    data.flush();
	}
    }

    void truckWaitLoad(Truck truck, WorkOrder workOrder)
    {
	Excavator excavator = workOrder.getExcavator();
	for (WorkOrder wo : data.getWorkOrders())
	{
	    if (wo.getExcavator().getId() == excavator.getId())
	    {
		if (wo.getStatus().equalsIgnoreCase(
			Constant.WORK_ORDER_STATUS_SCHEDULED))
		{
		    wo.setLoadActualStartTime(new Date());
		    data.update(wo);
		    truck.setStatus(data
			    .getTruckStatus(Constant.TRUCK_STATUS_LOAD));
		    data.update(truck);
		    data.flush();
		}
	    }
	}
    }

    void truckLoad(Truck truck, WorkOrder workOrder)
    {
	long now = System.currentTimeMillis();
	Excavator excavator = workOrder.getExcavator();
	for (WorkOrder wo : data.getWorkOrders())
	{
	    if (wo.getExcavator().getId() == excavator.getId())
	    {
		if (wo.getStatus().equalsIgnoreCase(
			Constant.WORK_ORDER_STATUS_SCHEDULED))
		{
		    long loadingTime = wo.getLoadScheduleEndTime().getTime()
			    - wo.getLoadScheduleStartTime().getTime();
		    if (wo.getLoadActualStartTime().getTime() + loadingTime < now)
		    {
			wo.setLoadActualEndTime(new Date());
			data.update(wo);
			truck.setStatus(data
				.getTruckStatus(Constant.TRUCK_STATUS_MOVE_TO_UNLOAD));
			data.update(truck);
			data.flush();
		    }
		}
	    }
	}
    }

    void truckMoveToUnload(Truck truck, WorkOrder workOrder)
    {
	int x = truck.getX();
	int y = truck.getY();
	long lastModifyTime = truck.getModifyTime().getTime();
	long now = System.currentTimeMillis();
	Edge edge = this.findOnEdge(x, y, workOrder.getUnloadRoute().getEdges());
	// calculate next position.
	double speed = truck.getModel().getCruisingSpeed();
	double deltaDistance = speed * (now - lastModifyTime) / 3600;
	double dx = x - edge.getToNode().getX();
	double dy = y - edge.getToNode().getY();
	double dist = Math.sqrt(dx * dx + dy * dy);
	int nextPositionX = 0;
	int nextPositionY = 0;

	if (dist > 0.0)
	{
	    if (deltaDistance >= dist)
	    {
		nextPositionX = edge.getToNode().getX();
		nextPositionY = edge.getToNode().getY();
		if (edge.getToNode().getId() == workOrder.getUnloadRoute()
			.getNodeTo().getId())
		{
		    truck.setStatus(data
			    .getTruckStatus(Constant.TRUCK_STATUS_WAIT_UNLOAD));
		}
	    }
	    else
	    {
		nextPositionX = (int) ((edge.getToNode().getX() - x)
			* (deltaDistance / dist) + x);
		nextPositionY = (int) ((edge.getToNode().getY() - y)
			* (deltaDistance / dist) + y);
	    }
	    truck.setX(nextPositionX);
	    truck.setY(nextPositionY);
	    truck.setModifyTime(new Date());
	    data.update(truck);
	    data.flush();
	}
	else
	{
	    // reached the destination, assertion is x,y is at the excavator
	    // position.
	    if (workOrder.getUnloadRoute().getNodeTo().getX() != x
		    || workOrder.getUnloadRoute().getNodeTo().getY() != y)
	    {
		logger
			.error("reached the destination, but x,y is not at the excavator position.");
	    }
	    truck.setModifyTime(new Date());
	    truck.setStatus(data
		    .getTruckStatus(Constant.TRUCK_STATUS_WAIT_UNLOAD));
	    data.update(truck);
	    data.flush();
	}
    }

    void truckWaitUnload(Truck truck, WorkOrder workOrder)
    {
	Excavator excavator = workOrder.getExcavator();
	for (WorkOrder wo : data.getWorkOrders())
	{
	    if (wo.getExcavator().getId() == excavator.getId())
	    {
		if (wo.getStatus().equalsIgnoreCase(
			Constant.WORK_ORDER_STATUS_SCHEDULED))
		{
		    wo.setUnloadActualStartTime(new Date());
		    data.update(wo);
		    truck.setStatus(data
			    .getTruckStatus(Constant.TRUCK_STATUS_UNLOAD));
		    data.update(truck);
		    data.flush();
		}
	    }
	}
    }

    void truckUnload(Truck truck, WorkOrder workOrder)
    {
	long now = System.currentTimeMillis();
	Excavator excavator = workOrder.getExcavator();
	for (WorkOrder wo : data.getWorkOrders())
	{
	    if (wo.getExcavator().getId() == excavator.getId())
	    {
		if (wo.getStatus().equalsIgnoreCase(
			Constant.WORK_ORDER_STATUS_SCHEDULED))
		{
		    long loadingTime = wo.getUnloadScheduleEndTime().getTime()
			    - wo.getUnloadScheduleStartTime().getTime();
		    if (wo.getUnloadActualStartTime().getTime() + loadingTime < now)
		    {
			wo.setUnloadActualEndTime(new Date());
			wo.setStatus(Constant.WORK_ORDER_STATUS_FINISHED);
			data.update(wo);
			truck.setStatus(data
				.getTruckStatus(Constant.TRUCK_STATUS_WAIT_INSTRUCTION));
			data.update(truck);
			data.flush();
		    }
		}
	    }
	}
    }

    Node findOnNode(int x, int y, Set<Edge> edges)
    {
	Node nodeFound = null;
	for (Edge edge : edges)
	{
	    Node n = edge.getFromNode();
	    if (x > LocationUtil.getScreenLocationX(n.getX())
		    - Constant.LineSelectionDistance / 2
		    && x < LocationUtil.getScreenLocationX(n.getX())
			    + Constant.LineSelectionDistance
		    && y > LocationUtil.getScreenLocationY(n.getY())
			    - Constant.LineSelectionDistance
		    && y < LocationUtil.getScreenLocationY(n.getY())
			    + Constant.LineSelectionDistance)
	    {
		nodeFound = n;
		break;
	    }
	}
	return nodeFound;
    }
    Edge findOnEdge(int x, int y, Set<Edge> edges)
    {
	Edge edgeFound = null;
	for (Edge edge : edges)
	{
	    if (x == edge.getFromNode().getX()
		    && y == edge.getFromNode().getY())
	    {
		return edge;
	    }
	    double distance = LocationUtil.distanceToSegment(LocationUtil
		    .getScreenLocationX(x), LocationUtil.getScreenLocationY(y),
		    LocationUtil.getScreenLocationX(edge.getFromNode().getX()),
		    LocationUtil.getScreenLocationY(edge.getFromNode().getY()),
		    LocationUtil.getScreenLocationX(edge.getToNode().getX()),
		    LocationUtil.getScreenLocationY(edge.getToNode().getY()));

	    if (distance < Constant.LineSelectionDistance)
	    {
		edgeFound = edge;
	    }
	}
	return edgeFound;
    }
*/
    List<Node> sortRoute(Set<Edge> edges, List<Node> sorted)
    {
	for (Edge edge : edges)
	{
	    Node current = sorted.get(sorted.size() - 1);
	    if (edge.getFromNode().getId() == current.getId())
	    {
		sorted.add(edge.getToNode());
	    }
	}
	if (sorted.size() == edges.size() + 1)
	{
	    return sorted;
	}
	else
	{
	    return sortRoute(edges, sorted);
	}
    }

    Edge findEdge(Node from, Set<Edge> edges)
    {
	for (Edge edge : edges)
	{
	    if (from.getId() == edge.getFromNode().getId())
	    {
		return edge;
	    }
	}
	return null;
    }
}
